#ifndef QTJSONSERIALIZER_SERIALIZERBASE_H
#define QTJSONSERIALIZER_SERIALIZERBASE_H

#include "QtJsonSerializer/qtjsonserializer_global.h"
#include "QtJsonSerializer/exception.h"
#include "QtJsonSerializer/typeconverter.h"
#include "QtJsonSerializer/qtjsonserializer_helpertypes.h"
#include "QtJsonSerializer/metawriters.h"
#include "QtJsonSerializer/typeextractors.h"

#include <tuple>
#include <optional>
#include <variant>

#include <QtCore/qobject.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qcborvalue.h>
#include <QtCore/qvariant.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qpointer.h>
#include <QtCore/qlist.h>

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtCore/qlinkedlist.h>
#endif

#include <QtCore/qvector.h>
#include <QtCore/qset.h>
#include <QtCore/qqueue.h>
#include <QtCore/qstack.h>
#include <QtCore/qhash.h>
#include <QtCore/qmap.h>

namespace QtJsonSerializer {

	class SerializerBasePrivate;
	//! A base class for the CBOR/JSON serializers
	class Q_JSONSERIALIZER_EXPORT SerializerBase : public QObject, protected TypeConverter::SerializationHelper
	{
		Q_OBJECT

			//! Specifies whether null for value types is allowed or not
			Q_PROPERTY(bool allowDefaultNull READ allowDefaultNull WRITE setAllowDefaultNull NOTIFY allowDefaultNullChanged)
			//! Specifies whether the `objectName` property of QObjects should be serialized
			Q_PROPERTY(bool keepObjectName READ keepObjectName WRITE setKeepObjectName NOTIFY keepObjectNameChanged)
			//! Specifies whether enums should be serialized as integer or as string
			Q_PROPERTY(bool enumAsString READ enumAsString WRITE setEnumAsString NOTIFY enumAsStringChanged)
			//! Specifies whether enums should be serialized as array of integers or as string
			Q_PROPERTY(bool versionAsString READ versionAsString WRITE setVersionAsString NOTIFY versionAsStringChanged)
			//! Specifies whether datetimes should be serialized as datetime string or as unix timestamp
			Q_PROPERTY(bool dateAsTimeStamp READ dateAsTimeStamp WRITE setDateAsTimeStamp NOTIFY dateAsTimeStampChanged)
			//! Specifies whether serializing a QLocale should use the bcp47 format
			Q_PROPERTY(bool useBcp47Locale READ useBcp47Locale WRITE setUseBcp47Locale NOTIFY useBcp47LocaleChanged)
			//! Specifies how strictly the serializer should verify data when deserializing
			Q_PROPERTY(ValidationFlags validationFlags READ validationFlags WRITE setValidationFlags NOTIFY validationFlagsChanged)
			//! Specifies how the serializer should treat polymorphism for QObject classes
			Q_PROPERTY(Polymorphing polymorphing READ polymorphing WRITE setPolymorphing NOTIFY polymorphingChanged)
			//! Specifies how multi maps and sets should be serialized
			Q_PROPERTY(MultiMapMode multiMapMode READ multiMapMode WRITE setMultiMapMode NOTIFY multiMapModeChanged)
			//! Specifies whether the STORED attribute on properties has any effect
			Q_PROPERTY(bool ignoreStoredAttribute READ ignoresStoredAttribute WRITE setIgnoreStoredAttribute NOTIFY ignoreStoredAttributeChanged)

	public:
		//! Flags to specify how strict the serializer should validate when deserializing
		enum class ValidationFlag {
			StandardValidation = 0x00, //!< Do not perform extra validation, only make sure types are valid and compatible
			NoExtraProperties = 0x01, //!< Make sure the json does not contain any properties that are not in the type to deserialize it to
			AllProperties = 0x02, //!< Make sure all properties of the type have a value in the deserialized json data
			StrictBasicTypes = 0x04, //!< Make shure basic types (string, int, ...) are actually of those types, instead of accepting all that are convertible

			FullPropertyValidation = (NoExtraProperties | AllProperties), //!< Validate properties are exactly the same as declared
			FullValidation = (FullPropertyValidation | StrictBasicTypes), //!< Validate everything
		};
		Q_DECLARE_FLAGS(ValidationFlags, ValidationFlag)
			Q_FLAG(ValidationFlags)

			//! Enum to specify the modes of polymorphism
			enum class Polymorphing {
			Disabled, //!< Do not serialize polymorphic and ignore information about classes in json
			Enabled, //!< Use polymorphism where declared by the classes/json
			Forced //!< Treat every object polymorphic, and required the class information to be present in json
		};
		Q_ENUM(Polymorphing)

			//! Enum to specify how multi maps and sets should be serialized
			enum class MultiMapMode {
			Map, //!< Store them as json object, with each element beeing a json array containing the actual values
			List, //!< Store a list of pairs, where for each pair the first element is the key and the second the value
			DenseMap //!< Just like Map, but entries with just one value are stored as that value, instead of an array with one element
		};
		Q_ENUM(MultiMapMode)

			//! Registers a custom extractor for the given type
			template<typename TType, typename TExtractor>
		static void registerExtractor();
		//! @copybrief SerializerBase::registerExtractor
		static void registerExtractor(int metaTypeId, const QSharedPointer<TypeExtractor>& extractor);

		//! Registers a custom type for list converisons
		template<typename T>
		static inline void registerListConverters();
		//! Registers a custom type for set converisons
		template<typename T>
		static inline void registerSetConverters();
		//! Registers a custom type for map converisons
		template<typename TKey, typename TValue, bool mapTypes = true, bool hashTypes = true>
		static inline void registerMapConverters();
		//! Registers a custom type for QSharedPointer and QPointer converisons
		template<typename T>
		static inline void registerPointerConverters();
		//! Registers a custom type for list, set map and optional converisons. Also include pointer converters, if applicable
		template<typename T>
		static inline void registerBasicConverters();
		//! Registers two types for pair conversion
		template<typename T, typename U>
		static inline void registerPairConverters();
		//! Registers a number of types for std::tuple conversion
		template<typename... TArgs>
		static inline void registerTupleConverters();
		//! Registers a custom type for std::optional converisons
		template<typename T>
		static inline void registerOptionalConverters();
		//! Registers a custom type for std::variant converisons
		template<typename... TArgs>
		static inline void registerVariantConverters();

		//! Serializes a given variant to either CBOR or JSON, depending on the actual instance
		virtual std::variant<QCborValue, QJsonValue> serializeGeneric(const QVariant& value) const = 0;
		//! Deserializes CBOR or JSON, depending on the actual instance, to variant
		virtual QVariant deserializeGeneric(const std::variant<QCborValue, QJsonValue>& value, int metaTypeId, QObject* parent = nullptr) const = 0;

		//! @readAcFn{QJsonSerializer::allowDefaultNull}
		bool allowDefaultNull() const;
		//! @readAcFn{QJsonSerializer::keepObjectName}
		bool keepObjectName() const;
		//! @readAcFn{QJsonSerializer::enumAsString}
		bool enumAsString() const;
		//! @readAcFn{QJsonSerializer::versionAsString}
		bool versionAsString() const;
		//! @readAcFn{QJsonSerializer::dateAsTimeStamp}
		bool dateAsTimeStamp() const;
		//! @readAcFn{QJsonSerializer::useBcp47Locale}
		bool useBcp47Locale() const;
		//! @readAcFn{QJsonSerializer::validationFlags}
		ValidationFlags validationFlags() const;
		//! @readAcFn{QJsonSerializer::polymorphing}
		Polymorphing polymorphing() const;
		//! @readAcFn{QJsonSerializer::multiMapMode}
		MultiMapMode multiMapMode() const;
		//! @readAcFn{QJsonSerializer::ignoreStoredAttribute}
		bool ignoresStoredAttribute() const;

		//! Globally registers a converter factory to provide converters for all QJsonSerializer instances
		template <typename TConverter, int Priority = TypeConverter::Priority::Standard>
		static void addJsonTypeConverterFactory();
		//! @copybrief SerializerBase::addJsonTypeConverterFactory()
		static void addJsonTypeConverterFactory(TypeConverterFactory* factory);

		//! Adds a custom type converter to this serializer
		template <typename TConverter>
		void addJsonTypeConverter();
		//! @copybrief SerializerBase::addJsonTypeConverter()
		void addJsonTypeConverter(const QSharedPointer<TypeConverter>& converter);

	public Q_SLOTS:
		//! @writeAcFn{QJsonSerializer::allowDefaultNull}
		void setAllowDefaultNull(bool allowDefaultNull);
		//! @writeAcFn{QJsonSerializer::keepObjectName}
		void setKeepObjectName(bool keepObjectName);
		//! @writeAcFn{QJsonSerializer::enumAsString}
		void setEnumAsString(bool enumAsString);
		//! @writeAcFn{QJsonSerializer::versionAsString}
		void setVersionAsString(bool versionAsString);
		//! @writeAcFn{QJsonSerializer::dateAsTimeStamp}
		void setDateAsTimeStamp(bool dateAsTimeStamp);
		//! @writeAcFn{QJsonSerializer::useBcp47Locale}
		void setUseBcp47Locale(bool useBcp47Locale);
		//! @writeAcFn{QJsonSerializer::validationFlags}
		void setValidationFlags(ValidationFlags validationFlags);
		//! @writeAcFn{QJsonSerializer::polymorphing}
		void setPolymorphing(Polymorphing polymorphing);
		//! @writeAcFn{QJsonSerializer::multiMapMode}
		void setMultiMapMode(MultiMapMode multiMapMode);
		//! @writeAcFn{QJsonSerializer::ignoreStoredAttribute}
		void setIgnoreStoredAttribute(bool ignoreStoredAttribute);

	Q_SIGNALS:
		//! @notifyAcFn{QJsonSerializer::allowDefaultNull}
		void allowDefaultNullChanged(bool allowDefaultNull, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::keepObjectName}
		void keepObjectNameChanged(bool keepObjectName, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::enumAsString}
		void enumAsStringChanged(bool enumAsString, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::versionAsString}
		void versionAsStringChanged(bool versionAsString, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::dateAsTimeStamp}
		void dateAsTimeStampChanged(bool dateAsTimeStamp, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::useBcp47Locale}
		void useBcp47LocaleChanged(bool useBcp47Locale, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::validationFlags}
		void validationFlagsChanged(ValidationFlags validationFlags, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::polymorphing}
		void polymorphingChanged(Polymorphing polymorphing, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::multiMapMode}
		void multiMapModeChanged(MultiMapMode multiMapMode, QPrivateSignal);
		//! @notifyAcFn{QJsonSerializer::ignoreStoredAttribute}
		void ignoreStoredAttributeChanged(bool ignoreStoredAttribute, QPrivateSignal);

	protected:
		//! Default constructor
		explicit SerializerBase(QObject* parent = nullptr);
		//! @private
		explicit SerializerBase(SerializerBasePrivate& dd, QObject* parent);

		virtual QList<int> typesForTag(QCborTag tag) const = 0;

		// protected implementation -> internal use for the type converters
		QVariant getProperty(const char* name) const override;
		QSharedPointer<const TypeExtractor> extractor(int metaTypeId) const override;
		QCborValue serializeSubtype(const QMetaProperty& property, const QVariant& value) const override;
		QCborValue serializeSubtype(int propertyType, const QVariant& value, const QByteArray& traceHint) const override;
		QVariant deserializeSubtype(const QMetaProperty& property, const QCborValue& value, QObject* parent) const override;
		QVariant deserializeSubtype(int propertyType, const QCborValue& value, QObject* parent, const QByteArray& traceHint) const override;

		//! @private
		QCborValue serializeVariant(int propertyType, const QVariant& value) const;
		//! @private
		QVariant deserializeVariant(int propertyType, const QCborValue& value, QObject* parent, bool skipConversion = false) const;

	private:
		Q_DECLARE_PRIVATE(SerializerBase)

			static void registerInverseTypedefImpl(int typeId, const char* normalizedTypeName);
	};

	//! A macro the mark a class as polymorphic
#define Q_JSON_POLYMORPHIC(x) \
	static_assert(std::is_same<decltype(x), bool>::value, "x must be bool"); \
	Q_CLASSINFO("polymorphic", #x)

// ------------- Generic Implementation -------------

	template<typename TType, typename TExtractor>
	void SerializerBase::registerExtractor()
	{
		registerExtractor(qMetaTypeId<TType>(), QSharedPointer<TExtractor>::create());
	}

	template<typename T>
	void SerializerBase::registerListConverters()
	{
		MetaWriters::SequentialWriter::registerWriter<QList, T>();
#if !defined(QT_NO_LINKED_LIST) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
		MetaWriters::SequentialWriter::registerWriter<QLinkedList, T>();
#endif
		MetaWriters::SequentialWriter::registerWriter<QVector, T>();
		MetaWriters::SequentialWriter::registerWriter<QStack, T>();
		MetaWriters::SequentialWriter::registerWriter<QQueue, T>();
	}

	template<typename T>
	void SerializerBase::registerSetConverters()
	{
		MetaWriters::SequentialWriter::registerWriter<QSet, T>();
	}

	template<typename TKey, typename TValue, bool mapTypes, bool hashTypes>
	void SerializerBase::registerMapConverters()
	{
		if constexpr (mapTypes) {
			MetaWriters::AssociativeWriter::registerWriter<QMap, TKey, TValue>();
			MetaWriters::AssociativeWriter::registerWriter<QMultiMap, TKey, TValue>();
		}
		if constexpr (hashTypes) {
			MetaWriters::AssociativeWriter::registerWriter<QHash, TKey, TValue>();
			MetaWriters::AssociativeWriter::registerWriter<QMultiHash, TKey, TValue>();
		}
	}

	template<typename T>
	void SerializerBase::registerPointerConverters()
	{
		registerExtractor<QSharedPointer<T>, TypeExtractors::SmartPointerExtractor<QSharedPointer, T>>();
		if constexpr (std::is_base_of_v<QObject, T>)
			registerExtractor<QPointer<T>, TypeExtractors::SmartPointerExtractor<QPointer, T>>();
	}

	template<typename T>
	void SerializerBase::registerBasicConverters()
	{
		if constexpr (std::is_base_of_v<QObject, T>) {
			registerBasicConverters<T*>();
			registerPointerConverters<T>();
			registerBasicConverters<QSharedPointer<T>>();
			registerBasicConverters<QPointer<T>>();
		}
		else {
			registerListConverters<T>();
			registerSetConverters<T>();
			registerMapConverters<QString, T>();
		}
	}

	template<typename T1, typename T2>
	void SerializerBase::registerPairConverters()
	{
		registerExtractor<QPair<T1, T2>, TypeExtractors::PairExtractor<QPair, T1, T2>>();
		registerExtractor<std::pair<T1, T2>, TypeExtractors::PairExtractor<std::pair, T1, T2>>();
	}

	template<typename... TArgs>
	void SerializerBase::registerTupleConverters()
	{
		registerExtractor<std::tuple<TArgs...>, TypeExtractors::TupleExtractor<TArgs...>>();
	}

	template<typename T>
	void SerializerBase::registerOptionalConverters()
	{
		registerExtractor<std::optional<T>, TypeExtractors::OptionalExtractor<T>>();
	}

	template<typename... TArgs>
	void SerializerBase::registerVariantConverters()
	{
		registerExtractor<std::variant<TArgs...>, TypeExtractors::VariantExtractor<TArgs...>>();
	}

	template<typename TConverter, int Priority>
	void SerializerBase::addJsonTypeConverterFactory()
	{
		static_assert(std::is_base_of<TypeConverter, TConverter>::value, "T must implement QJsonTypeConverter");
		addJsonTypeConverterFactory(new TypeConverterStandardFactory<TConverter, Priority>{});
	}

	template<typename TConverter>
	void SerializerBase::addJsonTypeConverter()
	{
		static_assert(std::is_base_of<TypeConverter, TConverter>::value, "T must implement QJsonTypeConverter");
		addJsonTypeConverter(QSharedPointer<TConverter>::create());
	}

}

Q_DECLARE_OPERATORS_FOR_FLAGS(QtJsonSerializer::SerializerBase::ValidationFlags)

Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(QMultiMap)
Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(QMultiHash)

//! @file serializerbase.h The SerializerBase header file
#endif // QTJSONSERIALIZER_SERIALIZERBASE_H
